package admin

import (
	"fmt"
	"go-amis-admin/amis"
	"go-amis-admin/utils"
	"reflect"
	"slices"
	"strconv"
	"strings"

	"gorm.io/gorm"
)

type CrudModule[T GormModel] struct {
	DB             *gorm.DB
	ViewModel      interface{}
	Label          string
	Name           string
	Icon           string
	Sort           uint
	Model          T
	BasePath       string
	PageSchema     amis.Obj
	EditSchema     amis.Obj
	CreateSchema   amis.Obj
	FilterSchema   amis.Obj
	Crud           amis.Obj
	Actions        []interface{}
	BulkActions    []amis.Obj
	ColActions     []amis.Obj
	FilterOptions  *FilterOptions
	FormOptions    *FormOptions
	ColumnsOptions *ColumnsOptions
	Permissions    PermList
	Children       []IModule
}

func NewCrudModule[T GormModel](label, name, icon, basePath string, sort uint, model T,
	formOpts *FormOptions, colOpts *ColumnsOptions, filterOpts *FilterOptions) CrudModule[T] {

	module := CrudModule[T]{
		DB:             Instance.DB,
		Label:          label,
		Name:           name,
		Icon:           icon,
		Sort:           sort,
		BasePath:       basePath + "/" + name,
		Model:          model,
		ColActions:     make([]amis.Obj, 0, 10),
		FormOptions:    formOpts,
		ColumnsOptions: colOpts,
		FilterOptions:  filterOpts,
		Permissions:    PermDefault(name),
	}
	module.init()
	module.UpdatePage()
	return module
}

func (m *CrudModule[T]) init() {
	m.initCreate()
	m.initEdit()
	m.initFilter()
	m.initColActions()
	m.initActions()
	m.initBulkActions()
}

func (m *CrudModule[T]) GetResource() Resource {
	return Resource{
		Label:     m.Label,
		Name:      m.Name,
		Icon:      m.Icon,
		Url:       "/" + m.Name,
		SchemaApi: "get:" + m.GetApiUrl("indexView"),
		PermList:  m.Permissions,
	}
}

func (m *CrudModule[T]) GetApiUrl(path string) string {
	return m.BasePath + "/" + path
}

func (m *CrudModule[T]) GetRouterPath(path string) string {
	return "/" + m.Name + "/" + path
}

func (m *CrudModule[T]) IndexView(ctx *AdminContext) {
	user := ctx.CurrentUser()
	m.ValidPerms(user)
	ctx.Sucess(m.PageSchema, "")
}

func (m *CrudModule[T]) ValidPerms(user *LoginInfo) {
	// 工具栏
	for _, action := range m.Actions {
		if obj, ok := action.(amis.Obj); ok {
			if obj["permCode"] == nil {
				continue
			}
			perCode := obj["permCode"].(string)
			if perCode != "" {
				if user.ID == 1 {
					obj["visible"] = true
				} else {
					obj["visible"], _ = Instance.Auth.HasPermissionForUser(
						fmt.Sprintf("%d", user.RoleID), m.Name, perCode)
				}
			}
		}
	}
	// 批量操作
	for _, action := range m.BulkActions {
		if action["permCode"] == nil {
			continue
		}
		perCode := action["permCode"].(string)
		if perCode != "" {
			if user.ID == 1 {
				action["visible"] = true
			} else {
				action["visible"], _ = Instance.Auth.HasPermissionForUser(
					fmt.Sprintf("%d", user.RoleID), m.Name, perCode)
			}
		}
	}
	for _, action := range m.ColActions {
		if action["permCode"] == nil {
			continue
		}
		perCode := action["permCode"].(string)
		if perCode != "" {
			if user.ID == 1 {
				action["visible"] = true
			} else {
				action["visible"], _ = Instance.Auth.HasPermissionForUser(
					fmt.Sprintf("%d", user.RoleID), m.Name, perCode)
			}
		}
	}
	m.initTable()
	m.initPage()
}

func (m *CrudModule[T]) EditView(ctx *AdminContext) {
	ctx.Sucess(m.EditSchema, "")
}

func (m *CrudModule[T]) UpdatePage() {
	m.initTable()
	m.initPage()
}

// 生成列表
func (m *CrudModule[T]) initPage() {
	if m.PageSchema == nil {
		m.PageSchema = amis.New("page").Set("name", m.Name).Set("body", []amis.Obj{m.Crud})
	} else {
		m.PageSchema.Set("body", []amis.Obj{m.Crud})
	}

}

func (m *CrudModule[T]) initActions() {
	actions := []interface{}{
		amis.New("columns-toggler").Set("align", "right"),
		amis.New("reload").Set("align", "right"),
		amis.NewAction("dialog").
			Set("target", "crud").
			Set("label", "新增").
			Set("permCode", "add").
			Set("level", "primary").
			Set("dialog", amis.New("dialog").Set("title", "新增").Set("body", m.CreateSchema)),
		"bulkActions",
		"filter-toggler",
	}
	if m.FilterOptions != nil && m.FilterOptions.KeywordFields != nil {
		actions = append(actions, amis.New("search-box").Set("name", "keywords"))
	}
	m.Actions = actions
}

func (m *CrudModule[T]) initBulkActions() {
	m.BulkActions = []amis.Obj{
		amis.NewAction("ajax").
			Set("target", "crud").
			Set("label", "批量删除").
			Set("permCode", "delete").
			Set("confirmText", "确定要批量删除?").
			Set("api", "delete:"+m.GetApiUrl("deleteBulk?ids=${ids|raw}")),
	}
}

func (m *CrudModule[T]) initColActions() {
	m.ColActions = []amis.Obj{
		{
			"tooltip":    "修改",
			"icon":       "fa fa-pencil",
			"type":       "button",
			"permCode":   "edit",
			"actionType": "dialog",
			"dialog": amis.Obj{
				"body": m.EditSchema,
			},
		},
		{
			"icon":        "fa fa-times text-danger",
			"tooltip":     "删除",
			"type":        "button",
			"permCode":    "delete",
			"actionType":  "ajax",
			"confirmText": "确认要删除？",
			"api":         "delete:" + m.GetApiUrl("delete?id=${ID}"),
		},
	}
}

func (m *CrudModule[T]) initTable() {

	crudView := amis.New("crud").
		Set("name", "crud").
		Set("syncLocation", false).
		Set("api", "get:"+m.GetApiUrl("getList")).
		Set("quickSaveApi", m.GetApiUrl("bulkUpdate?id=${ID}")).
		Set("quickSaveItemApi", m.GetApiUrl("quickUpdate?id=${ID}")).
		Set("filterDefaultVisible", false).
		SetWhere("filter", m.FilterSchema, func() bool { return m.FilterOptions != nil }).
		SetWhere("filterTogglable", amis.Obj{
			"label":       "更多搜索",
			"activeLabel": "收起搜索",
			"icon":        "fa fa-arrow-down mr-1",
			"activeIcon":  "fa fa-arrow-up mr-1",
		}, func() bool { return m.FilterOptions != nil && m.FilterOptions.FieldOptions != nil }).
		Set("keepItemSelectionOnPageChange", true).
		Set("maxKeepItemSelectionLength", 100).
		Set("labelTpl", "${ID}").
		Set("orderBy", "ID").
		Set("orderDir", "desc").
		Set("primaryField", "ID").
		Set("autoFillHeight", true).
		Set("headerToolbar", m.Actions).
		Set("bulkActions", m.BulkActions).
		Set("footerToolbar", []interface{}{"switch-per-page", "pagination"})

	tags := utils.StructTags(m.Model, "list")
	table := make([]amis.Obj, len(tags))
	for _, v := range tags {
		if m.ColumnsOptions != nil {
			if m.ColumnsOptions.ListFields != nil {
				if !slices.Contains(m.ColumnsOptions.ListFields, v["name"].(string)) {
					continue
				}
			}
			if m.ColumnsOptions.ExcludeFields != nil {
				if slices.Contains(m.ColumnsOptions.ExcludeFields, v["name"].(string)) {
					continue
				}
			}
		}
		col := amis.Obj{
			"name":  v["name"],
			"label": v["label"],
		}
		if m.ColumnsOptions != nil {
			opt := m.ColumnsOptions.FieldOptions[v["name"].(string)]
			col.SetOptions(opt)
		}
		table[v["index"].(int)] = col
	}
	table = append(table, amis.New("operation").
		Set("label", "操作").
		Set("width", 100).
		Set("buttons", m.ColActions))
	crudView.Set("columns", table)
	m.Crud = crudView
}

func (m *CrudModule[T]) initEdit() {
	form := amis.New("form").SetOptions(amis.Options{
		"title": "编辑",
		"api":   "post:" + m.GetApiUrl("save"),
		"mode":  "horizontal",
	})
	tags := utils.StructTags(m.Model, "edit")
	formItems := make([]amis.Obj, len(tags))

	for k, v := range tags {
		t := "input-text"
		if k == "ID" {
			t = "hidden"
		}
		item := amis.New(t)
		item.SetOptions(v)
		if m.FormOptions != nil {
			item.SetOptions(m.FormOptions.FieldOptions[k])
		}
		formItems[v["index"].(int)] = item
	}
	m.EditSchema = form.Set("body", formItems)
}

func (m *CrudModule[T]) initCreate() {
	form := amis.New("form").SetOptions(amis.Options{
		"title": "编辑",
		"api":   "post:" + m.GetApiUrl("save"),
		"mode":  "horizontal",
	})
	tags := utils.StructTags(m.Model, "create")
	formItems := make([]amis.Obj, len(tags))

	for k, v := range tags {
		t := "input-text"
		if k == "ID" {
			t = "hidden"
		}
		item := amis.New(t)
		item.SetOptions(v)
		if m.FormOptions != nil {
			item.SetOptions(m.FormOptions.FieldOptions[k])
		}
		formItems[v["index"].(int)] = item
	}
	m.CreateSchema = form.Set("body", formItems)
}

func (m *CrudModule[T]) initFilter() {
	if m.FilterOptions == nil || m.FilterOptions.FieldOptions == nil {
		return
	}
	filter := amis.New("group")
	body := make([]amis.Obj, 0)
	if m.FilterOptions.FieldOptions != nil {
		for field, opts := range m.FilterOptions.FieldOptions {
			if t, ok := opts["type"]; ok {
				if t.(string) == "-" {
					continue
				}
				body = append(body, amis.New(t.(string)).SetOptions(opts).Set("name", field))
			} else {
				body = append(body, amis.New("input-text").SetOptions(opts).Set("name", field))
			}
		}
	}
	filter.Set("body", body)
	filter.Set("actions", []amis.Obj{
		amis.New("reset").Set("label", "重置"),
		amis.New("submit").Set("label", "查询").Set("level", "primary")})
	m.FilterSchema = filter
}

func (m *CrudModule[T]) GetListApi(ctx *AdminContext) {
	var search amis.BaseQuery
	var where = make(map[string]string)
	if err := ctx.ShouldBindQuery(&search); err != nil {
		ctx.Fail(err.Error())
		return
	}
	exclude_fields := []string{"page", "perPage", "keywords", "orderBy", "orderDir"}
	for k, v := range ctx.Request.URL.Query() {
		if !slices.Contains(exclude_fields, k) {
			where[k] = v[0]
		}
	}
	if data, err := m.GetList(&search, where); err != nil {
		ctx.Fail(err.Error())
	} else {
		ctx.Sucess(data, "success")
	}
}

func (m *CrudModule[T]) GetByIDApi(ctx *AdminContext) {
	id, _ := strconv.ParseUint(ctx.Query("id"), 10, 64)
	if model, err := m.GetByID(uint(id)); err != nil {
		ctx.Fail(err.Error())
	} else {
		ctx.Sucess(model, "")
	}
}

func (m *CrudModule[T]) SaveApi(ctx *AdminContext) {
	model := new(T)
	if err := ctx.ShouldBind(model); err != nil {
		ctx.Fail(err.Error())
		return
	}
	if err := m.Save(model); err != nil {
		ctx.Fail(err.Error())
		return
	}
	ctx.Sucess(nil, "success")
}

func (m *CrudModule[T]) BulkUpdateApi(ctx *AdminContext) {
	editModel := new(EditModel[T])
	if err := ctx.BindJSON(&editModel); err != nil {
		ctx.Fail(err.Error())
		return
	}
	if err := m.BulkUpdate(editModel.Rows); err != nil {
		ctx.Fail(err.Error())
		return
	}
	ctx.Sucess(nil, "success")
}

func (m *CrudModule[T]) QuickUpdateApi(ctx *AdminContext) {
	model := new(T)
	id := ctx.GetUint("ID")
	if err := ctx.ShouldBind(model); err != nil {
		ctx.Fail(err.Error())
		return
	}
	if err := m.Update(id, model); err != nil {
		ctx.Fail(err.Error())
		return
	}
	ctx.Sucess(nil, "success")
}

func (m *CrudModule[T]) DeleteApi(ctx *AdminContext) {
	id, _ := strconv.ParseUint(ctx.Query("id"), 10, 64)
	err := m.Delete(uint(id))

	if err != nil {
		ctx.Fail(err.Error())
		return
	}
	ctx.Sucess(nil, "success")
}

func (m *CrudModule[T]) DeleteBulkApi(ctx *AdminContext) {
	ids := ctx.Query("ids")
	arr, _ := utils.StringToArray(ids, utils.IntParser{})
	err := m.BulkDelete(arr)

	if err != nil {
		ctx.Fail(err.Error())
		return
	}
	ctx.Sucess(nil, "success")
}

func (m *CrudModule[T]) querySet() *gorm.DB {
	query := m.DB.Model(new(T))
	t := reflect.TypeOf(m.Model)
	for i := 0; i < t.NumField(); i++ {
		// 判断是否是外键，如果是外键预加载
		field := t.Field(i)
		if field.Type.Kind() == reflect.Struct {
			// 判断指针指向的类型是否包含 gorm.Model
			for i := 0; i < field.Type.NumField(); i++ {
				if field.Type.Field(i).Type == reflect.TypeOf(gorm.Model{}) {
					query.Preload(field.Name)
					break
				}
			}
		}
	}
	return query
}

func (m *CrudModule[T]) GetList(params *amis.BaseQuery, query map[string]string) (*amis.PageData, error) {

	data := make([]T, 0)
	tx := m.querySet().
		Limit(params.PerPage).
		Offset((params.Page - 1) * params.PerPage)
	if params.OrderBy != "" {
		if params.OrderDir == "" {
			params.OrderDir = "asc"
		}
		tx.Order(params.OrderBy + " " + params.OrderDir)
	}
	if params.Keywords != "" {
		kvWhere := "1<>1 "
		for _, field := range m.FilterOptions.KeywordFields {
			kvWhere += fmt.Sprintf(" or %s like '%%%s%%'", field, params.Keywords)
		}
		tx.Where(kvWhere)
	}
	for k, v := range query {
		if v == "" {
			continue
		}
		opt := amis.Options{}
		if m.FilterOptions != nil && m.FilterOptions.FieldOptions != nil {
			opt = m.FilterOptions.FieldOptions[k]
		}

		valueType := opt["valueType"]
		if valueType == nil {
			valueType = ValueTypeString
		}
		operator := opt["operator"]

		if operator != nil {
			if fn, ok := operator.(func(*gorm.DB, string, string)); ok {
				fn(tx, k, v)
			} else if operator.(WhereOperator) == WhereIn {
				if valueType.(ValueType) == ValueTypeInt {
					val, _ := utils.StringToArray(v, utils.IntParser{})
					tx.Where(k+" in ?", val)
				} else {
					tx.Where(k+" in ?", strings.Split(v, ","))
				}
			} else if operator.(WhereOperator) == WhereBetween {
				if valueType.(ValueType) == ValueTypeInt {
					val, _ := utils.StringToArray(v, utils.IntParser{})
					tx.Where(k+" between ? and ? ", val[0], val[1])
				} else if valueType.(ValueType) == ValueTypeFloat {
					val, _ := utils.StringToArray(v, utils.FloatParser{})
					tx.Where(k+" between ? and ? ", val[0], val[1])
				} else {
					val := strings.Split(v, ",")
					tx.Where(k+" between ? and ? ", val[0], val[1])
				}
			} else {
				var val interface{}
				switch valueType.(ValueType) {
				case ValueTypeInt:
					val, _ = strconv.Atoi(v)
				case ValueTypeFloat:
					val, _ = strconv.ParseFloat(v, 64)
				case ValueTypeBool:
					val, _ = strconv.ParseBool(v)
				default:
					val = v
				}
				tx.Where(k+operator.(WhereOperator).String()+" ? ", val)
			}
		} else {
			var val interface{}
			switch valueType.(ValueType) {
			case ValueTypeInt:
				val, _ = strconv.Atoi(v)
			case ValueTypeFloat:
				val, _ = strconv.ParseFloat(v, 64)
			case ValueTypeBool:
				val, _ = strconv.ParseBool(v)
			default:
				val = v
			}
			tx.Where(k+" = ? ", val)
		}
	}
	tx.Find(&data)
	var count int64
	m.DB.Model(new(T)).Count(&count)
	return &amis.PageData{Items: data, Total: count}, nil
}

func (m *CrudModule[T]) Save(model *T) error {
	if (*model).PK() > 0 {
		return m.DB.Updates(model).Where("id=?", (*model).PK()).Error
	} else {
		return m.DB.Create(model).Error
	}
}

func (m *CrudModule[T]) BulkUpdate(data []T) error {
	for _, row := range data {
		if err := m.DB.Updates(&row).Error; err != nil {
			return err
		}
	}
	return nil
}

func (m *CrudModule[T]) Update(id uint, model *T) error {
	return m.DB.Updates(model).Where("id=?", id).Error
}

func (m *CrudModule[T]) GetByID(id uint) (*T, error) {
	r := new(T)
	err := m.DB.First(r, id).Error
	return r, err
}

func (m *CrudModule[T]) Delete(id uint) error {
	return m.DB.Delete(new(T), id).Error
}

func (m *CrudModule[T]) BulkDelete(ids []int) error {
	return m.DB.Delete(new(T), "id in ?", ids).Error
}
